
#include "memory.h"


Memory::Memory(uint32_t threads,uint32_t pagesize,uint32_t LMsize){

    _LM = new std::map<uint64_t,uint32_t> [threads];
    _preLM = new std::map<uint64_t,uint32_t> [threads];
    _LMnumpages = new uint32_t [threads];
    memset(_LMnumpages,0,sizeof(uint32_t)*threads);
    _pagesize=pagesize;
    _LMsize=LMsize;
    _threads=threads;
    _LMcount=0;
    _migrations = new uint32_t [threads];
    _lastmigration = new uint32_t [threads];
    _migrationSuccess = new std::vector<pair_value> [threads];
    memset(_migrations,0,sizeof(uint32_t)*threads);
    _sortedAccesses = new std::vector<pair_page> [threads];
}

Memory::~Memory() {
    if (_LMnumpages!=NULL) {
        delete []_LMnumpages;
    }
    _LM->clear();
    if (_LM!=NULL) {
        delete [] _LM;
    }
    _preLM->clear();
    if (_preLM!=NULL) {
        delete [] _preLM;
    }
    if (_migrations!=NULL) {
        delete [] _migrations;
        delete [] _lastmigration;
    }
    if (_migrationSuccess!=NULL) {
        delete [] _migrationSuccess;
    }

    if (_sortedAccesses!=NULL) {
        delete [] _sortedAccesses;
    }

}

void
Memory::setLMSize(uint32_t LMSize)
{
    _LMsize=LMSize;
}

void
Memory::setMigrate(uint32_t migratePolicy)
{
    _migrate=migratePolicy;
}


void
Memory::reset()
{
    memset(_LMnumpages,0,sizeof(uint32_t)*_threads);
    memset(_migrations,0,sizeof(uint32_t)*_threads);
    memset(_lastmigration,0,sizeof(uint32_t)*_threads);
    _LMcount=0;
    _pagetable.clear();
    for (int i=0;i<_threads;i++) {
         _LM[i].clear();
         _preLM[i].clear();
         _migrationSuccess[i].clear();
         _sortedAccesses[i].clear();
    }
}


/*  Sort from most accessed to least accessed */
bool sortPages( pair_page A,  pair_page B)
{
        //return A.second > B.second;
        return A.second < B.second;
}

/* Dump Pagetable content (page,number, accesses) */
void
Memory::dumpPageTable(std::ofstream & out)
{
    std::map<uint64_t,PageInfo>::iterator iter;
    std::vector<pair_page> sorted;
    std::vector<pair_page>::iterator iter2;

    for(iter=_pagetable.begin();iter!=_pagetable.end();iter++) {
        sorted.push_back(std::make_pair(iter->first,iter->second._accesses));
    }
    std::sort(sorted.begin(), sorted.end(), sortPages);
    uint32_t i=1;
    for(iter2=sorted.begin();iter2!=sorted.end();iter2++,i++) {
        out <<i<<","<<std::hex<<"0x"<<iter2->first<<","<<std::dec<<iter2->second<<std::endl;
    }
}

/* Dump Pagetable content (page,number, accesses) */
void
Memory::dumpExtendedPageTable(std::ofstream & out,uint32_t threshold)
{
    std::map<uint64_t,PageInfo>::iterator iter;

    for(iter=_pagetable.begin();iter!=_pagetable.end();iter++) {
        for (uint32_t tid=0;tid<_threads;tid++) {
            out <<tid<<","<<threshold<<","<<std::hex<<"0x"<<iter->first<<","<<std::dec<<
                iter->second._tidaccesses[tid].getMM()<<","<<iter->second._tidaccesses[tid].getLM()
                <<","<<iter->second._tidaccesses[tid].getRLM()<<std::endl;
        }
    }
}



uint64_t Memory::getPageInfo(uint64_t page,uint32_t *accesses,uint32_t *location){
    std::map<uint64_t,PageInfo>::iterator iter;

    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        *location=iter->second._location;
        *accesses=iter->second._accesses;
        return page;
    }
    return 0;
}

/* Migrate Page to location */
uint32_t Memory::migrate(uint64_t page,uint32_t thread,uint32_t location) {
    std::map<uint64_t,PageInfo>::iterator iter;

    // Is there space available
    if (_LMnumpages[location]>=_LMsize) {
        return 0;
    }
    _migrations[thread]++;

    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        // Take out from another LocalMemory
        if (iter->second._location!=MAINMEMORY) {
            uint32_t src=iter->second._location;
            std::map<uint64_t,uint32_t>::iterator it;
            for (it=_LM[src].begin(); it!=_LM[src].end(); it++) {
                if (it->first==page){
                    _LM[src].erase(it);
                    _LMnumpages[src]--;
                    break;
                }
            }
        }
        if (location==MAINMEMORY) {
            iter->second._location=MAINMEMORY;
            return 1;
        }
        // Allocate in another LM
        iter->second._location=thread;
        std::pair<uint64_t,uint32_t> data(page,iter->second._tidaccesses[location].getTotalAccesses());
        _LM[location].insert(data);
        _LMnumpages[location]++;
        PageInfo info(location,0);
        return 1;
    }
    else  {
        std::pair<uint64_t,uint32_t> data(page,iter->second._tidaccesses[location].getTotalAccesses());
        _LM[location].insert(data);
        _LMnumpages[location]++;
        PageInfo info(location,iter->second._tidaccesses[location].getTotalAccesses());
        std::pair<uint64_t,PageInfo> data2(page,info);
        _pagetable.insert(data2);
        return 1;
    }
    return 0;
}


struct sort_pred {
    bool operator()(pair_page left, pair_page right) {
        return left.second > right.second;
    }
};


std::vector<pair_value>
Memory::getMigrationSuccess(uint32_t thread)
{
    return _migrationSuccess[thread];
}

void
Memory::calculateMigrationSuccess()
{
    std::map<uint64_t,PageInfo>::iterator iter;
    std::map<uint64_t,uint32_t>::iterator tidIter;
    int32_t success=0;
    uint32_t location=MAINMEMORY;

 /* Ponderate Migration Performance */
    for (int i=0;i<_threads;i++) {
        success=0;
        for(tidIter=_preLM[i].begin();tidIter!=_preLM[i].end();tidIter++) {
            location=MAINMEMORY;
            iter=_pagetable.find(tidIter->first);
            if (iter!=_pagetable.end()) {
                location = iter->second._location;
            }
            if (location!=MAINMEMORY) {
                std::map<uint64_t,uint32_t>::iterator iter2;
                for(iter2=_LM[location].begin();iter2!=_LM[location].end();iter2++) {
                    if (iter2->first==tidIter->first) {
                        success+=(iter2->second-tidIter->second);
                    }
                }
            }
            else {
                // migrated to main memory is negative
                success+=-(iter->second._tidaccesses[i].getTotalAccesses()-tidIter->second);
            }

        }
        // success and number of  migrations in last period
        _lastmigration[i]=_migrations[i]-_lastmigration[i];
        _migrationSuccess[i].push_back(std::make_pair(success,_lastmigration[i]));
        _lastmigration[i]=_migrations[i];
    }
}


bool
Memory::findLMVictim(uint32_t tid,uint64_t page,uint32_t accesses,uint64_t &victim)
{
    std::map<uint64_t,uint32_t>::iterator it;
    uint32_t victimAccesses=0xFFFFFFFF;
    victim=0;

    if (_LMnumpages[tid]<_LMsize) {
        // no replacement necessary
        return true;
    }

    for(it=_LM[tid].begin();it!=_LM[tid].end();it++) {
        if (it->first==page) {
           return false;
        }
        else {
            if (it->second<accesses)
                /* check best victim */
                if (it->second<victimAccesses) {
                    victim=it->first;
                    victimAccesses=it->second;
                }
        }
    }

    if (victim!=0)
        return true;
    else
        return false;
}

void
Memory::resetPageTable()
{
    std::map<uint64_t,PageInfo>::iterator iter;

    for(iter=_pagetable.begin();iter!=_pagetable.end();iter++)
    {
        iter->second._accesses=0;
        for (int i=0;i<_threads;i++ ) {
           iter->second._tidaccesses[i].reset();
        }
    }
    return;
}

/* Main Dynamic Migration Algorithm */
void
Memory::do_ProfileDynamic_migrate()
{
    std::map<uint64_t,PageInfo>::iterator iter;

    // ponderate previous migrations
    calculateMigrationSuccess();
    //resetPageTable();

    std::map<uint64_t,uint32_t>::iterator it1;
    std::vector<pair_page>::iterator it;

    /* reduce access counter */
    for (int tid=0;tid<_threads;tid++) {
        for(it1=_LM[tid].begin();it1!=_LM[tid].end();it1++) {
            it1->second=0;
        }
     }


    // Re-allocate pages -  Migrate most accessed pages
    // Most accessed Pages are sorted migration should be round robin
    for (int tid=0;tid<_threads;tid++) {
        uint32_t round_migrations=0;
        std::cout << "Thread:"<<tid<<"--LMsize:"<<_LMnumpages[tid] <<" MostAccessLen:"<<_sortedAccesses[tid].size()<<std::endl;
        for(it=_sortedAccesses[tid].begin();it!=_sortedAccesses[tid].end() && round_migrations<_LMsize;it++) {
            uint64_t victim=0;
            uint64_t newpage=it->first;
            iter = _pagetable.find(newpage);
            uint32_t location = iter->second._location;
            if (location==MAINMEMORY ||
               (iter->second._tidaccesses[tid].getTotalAccesses()>iter->second._tidaccesses[location].getTotalAccesses())) {
                /* check victim or space in LM */
                if (findLMVictim(tid,newpage,iter->second._tidaccesses[tid].getTotalAccesses(),victim)) {
                    if (victim!=0)
                        migrate(victim,tid,MAINMEMORY);
                    migrate(newpage,tid,tid);
                    round_migrations++;
                }
            }
        }
    }
    // Save previous accesses
    for (int i=0;i<_threads;i++) {
        _preLM[i].clear();
        for(it1=_LM[i].begin();it1!=_LM[i].end();it1++) {
            std::pair<uint64_t,uint32_t> data(it1->first,it1->second);
            _preLM[i].insert(data);
        }
    }

    deleteMostAccess();

}


void
Memory::insertMostAccess(uint64_t page,uint32_t thread,uint32_t accesses)
{
    std::vector<pair_page>::iterator iter;
    bool found=false;

    for(iter=_sortedAccesses[thread].begin();iter!=_sortedAccesses[thread].end();iter++) {
        if (iter->first==page) {
            iter->second+=1;
            found=true;
            break;
        }
    }
    if (!found) {
        _sortedAccesses[thread].push_back(std::make_pair(page,accesses));
    }

    std::sort(_sortedAccesses[thread].begin(), _sortedAccesses[thread].end(), sortPages);
    if (_sortedAccesses[thread].size()>_LMsize*_threads) {
        _sortedAccesses[thread].pop_back();
    }
}

void
Memory::deleteMostAccess()
{
    for (int i=0;i<_threads;i++) {
        _sortedAccesses[i].clear();
    }
}

/* Record an access to a memory page by thread thread*/
uint32_t Memory::access(uint64_t page,uint32_t thread)
{
    std::map<uint64_t,PageInfo>::iterator iter;

    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
            uint32_t location =  iter->second._location;
            iter->second._tidaccesses[thread].Access(location,thread);
            iter->second._accesses++;
            insertMostAccess(page,thread,iter->second._tidaccesses[thread].getTotalAccesses());
            if (location!=MAINMEMORY) {
                // update LM accesses
                std::map<uint64_t,uint32_t>::iterator tidIter;
                for(tidIter=_LM[location].begin();tidIter!=_LM[location].end();tidIter++) {
                    if (tidIter->first==page) {
                        tidIter->second+=1;
                        break;
                    }
                }
            }
            return location;
    }
    else {
        PageInfo info(MAINMEMORY,1);
        info._tidaccesses[thread].Access(MAINMEMORY,thread);
        std::pair<uint64_t,PageInfo> data (page,info);
        _pagetable.insert(data);
        insertMostAccess(page,thread,1);
        return MAINMEMORY;
    }
}

uint32_t
Memory::getNumPages(uint32_t location)
{
    return _LMnumpages[location];
}

void
Memory::setPage(uint64_t page,uint32_t location) {
    std::map<uint64_t,PageInfo>::iterator iter;

    if (page==ALLMEMORY) {
        for(iter=_pagetable.begin();iter!=_pagetable.end();iter++) {
            iter->second._location=MAINMEMORY;
        }
        return;
    }
    migrate(page,location,location);
    // we do not consider migration , initial allocations
     _migrations[location]--;
}

void
Memory::resetAllCacheLocation(uint64_t lineTag,int cacheLevel)
{
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(lineTag);
    if (iter!=_pagetable.end()) {
        if (cacheLevel==1)
            iter->second._L1Location.reset();
        else
            iter->second._L2Location.reset();
    }
    else {
        PageInfo info;
        if (cacheLevel==1)
            info._L1Location.reset();
        else
            info._L2Location.reset();
        std::pair<uint64_t,PageInfo> data (lineTag,info);
        _pagetable.insert(data);
    }
}

void
Memory::resetCacheLocation(uint64_t lineTag,int cacheLevel,uint32_t thread)
{
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(lineTag);
    if (iter!=_pagetable.end()) {
        if (cacheLevel==1)
            iter->second._L1Location.reset(thread);
        else
            iter->second._L2Location.reset(thread);
    }
    else {
        PageInfo info;
        if (cacheLevel==1)
            info._L1Location.reset(thread);
        else
            info._L2Location.reset(thread);
        std::pair<uint64_t,PageInfo> data (lineTag,info);
        _pagetable.insert(data);
    }
}

void
Memory::setCacheLocation(uint64_t lineTag,int cacheLevel,uint32_t thread)
{
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(lineTag);
    if (iter!=_pagetable.end()) {
        if (cacheLevel==1)
            iter->second._L1Location.set(thread,true);
        else
            iter->second._L2Location.set(thread,true);
    }
    else {
        PageInfo info;
        if (cacheLevel==1)
            info._L1Location.set(thread,true);
        else
            info._L2Location.set(thread,true);
        std::pair<uint64_t,PageInfo> data(lineTag,info);
        _pagetable.insert(data);
    }
}

void
Memory::getCacheLocation(uint64_t line,std::bitset<MAXTHREADS> & L1,std::bitset<MAXTHREADS> & L2)
{
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(line);
    if (iter!=_pagetable.end()) {
        L1=iter->second._L1Location;
        L2=iter->second._L2Location;
    }
}

void
Memory::setLocation(uint64_t page,uint32_t location)
{

    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        iter->second._location=location;
    }
}

uint32_t
Memory::getLocation(uint64_t page)
{
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        return  iter->second._location;
    }
    else {
        return MAINMEMORY;
    }
}

uint32_t Memory::getPageAccesses(uint64_t page,uint32_t thread){
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        return iter->second._tidaccesses[thread].getAccesses();
    }
    return 0;
}

void Memory::resetPageAccesses(uint64_t page,uint32_t thread){
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        iter->second._tidaccesses[thread].resetAccesses();
    }
}
void Memory::resetPageAccesses(uint64_t page){
    std::map<uint64_t,PageInfo>::iterator iter;
    iter=_pagetable.find(page);
    if (iter!=_pagetable.end()) {
        for (int i=0;i<_threads;i++) {
            iter->second._tidaccesses[i].resetAccesses();
        }
    }
}
